using System;
using System.Collections.Generic;
using System.Text;
using Ndo.Text;

namespace Ndo.Maths
{
    public class Rational<TInteger> : Real<Rational<TInteger>>
        where TInteger : Integer<TInteger>, new()
    {
        public Rational() : this(Number<TInteger>.One, Number<TInteger>.One, false)
        {}
        public Rational(TInteger value)
            : this(value, Number<TInteger>.One, false)
        {}
        [ParsingPattern(@"\({0}/{1}\)|\({0}\)")]
        public Rational(TInteger numerator, TInteger denominator) : this(numerator, denominator, true)
        {}
        private Rational(TInteger numerator, TInteger denominator, bool check)
        {
            if (check)
            {
                if(denominator == null)
                    denominator = Number<TInteger>.One;
                if (numerator == null)
                    throw new ArgumentNullException();
                SignTypes sn = numerator.Sign, sd = denominator.Sign;
                bool b = false;
                switch (sd)
                {
                    case SignTypes.Zero:
                        throw new DivideByZeroException();
                    case SignTypes.Negative:
                        b = true;
                        denominator = denominator.GetNegation();
                        break;
                }
                switch (sn)
                {
                    case SignTypes.Negative:
                        b ^= b;
                        numerator = numerator.GetNegation();
                        break;
                    case SignTypes.Zero:
                        this.numerator = Number<TInteger>.Zero;
                        this.denominator = Number<TInteger>.One;
                        return;
                }
                TInteger i = Integer<TInteger>.GCD(numerator, denominator);
                this.numerator = numerator.DividedBy(i);
                this.denominator = denominator.DividedBy(i);
                if (b)
                    this.numerator = this.numerator.GetNegation();
            }
            else
            {
                this.numerator = numerator;
                this.denominator = denominator;
            }
        }
        public override string ToString()
        {
            return string.Format("({0}/{1})", numerator, denominator);
        }
        private readonly TInteger numerator;
        private readonly TInteger denominator;
        public TInteger Numerator { get { return this.numerator; } }
        public TInteger Denominator { get { return this.denominator; } }
        public bool IsProperFraction{get{ return One.Inequals(denominator);}}
        public TInteger GetMixedFraction(out Rational<TInteger> r)
        {
            TInteger n, i;
            i = numerator.DivRem(denominator, out n);
            r = new Rational<TInteger>(n, denominator, false);
            return i;
        }
        public override SignTypes Sign
        {
            get { return numerator.Sign; }
        }

        public static Rational<TInteger> operator +(Rational<TInteger> r1, Rational<TInteger> r2)
        {
            return new Rational<TInteger>(r1.numerator.MultipliedBy(r2.denominator).Add(r2.numerator.MultipliedBy(r1.denominator)), r1.denominator.MultipliedBy(r2.denominator));
        }
        public static Rational<TInteger> operator -(Rational<TInteger> r1, Rational<TInteger> r2)
        {
            return new Rational<TInteger>(r1.numerator.MultipliedBy(r2.denominator).SubtractedFrom(r2.numerator.MultipliedBy(r1.denominator)), r1.denominator.MultipliedBy(r2.denominator));
        }
        public static Rational<TInteger> operator *(Rational<TInteger> r1, Rational<TInteger> r2)
        {
            return new Rational<TInteger>(r1.numerator.MultipliedBy(r2.numerator), r1.denominator.MultipliedBy(r2.denominator));
        }
        public static Rational<TInteger> operator /(Rational<TInteger> r1, Rational<TInteger> r2)
        {
            if (r2.Sign == SignTypes.Zero)
                throw new DivideByZeroException();
            return new Rational<TInteger>(r1.numerator.MultipliedBy(r2.denominator), r1.denominator.MultipliedBy(r2.numerator));
        }

        public static bool operator ==(Rational<TInteger> r1, Rational<TInteger> r2)
        {
            return r1.Equals(r2);
        }

        public static bool operator !=(Rational<TInteger> r1, Rational<TInteger> r2)
        {
            return !r1.Equals(r2);
        }

        public override bool Equals(Rational<TInteger> other)
        {
            return this.numerator.Equals(other.numerator) && this.denominator.Equals(other.denominator);
        }

        public override int GetHashCode()
        {
            return this.numerator.GetHashCode() ^ this.denominator.GetHashCode();
        }

        public static implicit operator Rational<TInteger>(TInteger i)
        {
            return new Rational<TInteger>(i);
        }

    }
}
